# -*- coding:latin-1 -*-

#####################################
# Date: 05/04/10                    #
# Auteur: Rigaut Maximilien         #
# Nom: Gomoku  [Graphix]            #
# Version: 1.0                      #
# Copyright 2010: Rigaut Maximilien #
#####################################
#    This file is part of Gomoku.
#
#    Gomoku is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Gomoku is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Gomoku.  If not, see <http://www.gnu.org/licenses/>.
#################################

#======================+
#  Sous-Modules        |
#----------------------+
from Tkinter import *
from tkMessageBox import *
import Tix

#======================+
#  Classes             |
#----------------------+

class morpion(Tk):
    """Classe principale de jeu, il s'agit de toute l'interface graphique. \n Censee interagir avec le joueur."""
    def __init__(self):
        "Fonction d'initialisation, prepare le module pour lancer une partie"
        Tk.__init__(self)
        self.menuInit() # Initialise les menus
        self.canevasInit() # Initialise le plateau de jeu
        self.newGame() # Lance une nouvelle partie
        self.resizable(width=0, height=0)
    def menuInit(self):
        "[]: Initialisation des menus.\n Reserve a l'application."
        menuBar=Menu(self)
        fileMenu = Menu(menuBar,tearoff=0)
        helpMenu = Menu(menuBar,tearoff=0)
        # Menu 1: Fichier
        fileMenu.add_command(label=langue['settings'], underline=0, command= self.settingsMenu)
        fileMenu.add_separator()
        fileMenu.add_command(label=langue['new_game'], underline=0, command= self.newGame)
        fileMenu.add_command(label=langue['undo'], underline=0, command= self.undo)
        fileMenu.add_separator()
        fileMenu.add_command(label=langue['quit'], underline=0, command= self.quit)
        menuBar.add_cascade(label=langue["file"], menu=fileMenu)
        # Menu 2: Aide
        helpMenu.add_command(label=langue['about'], underline=0, command= self.aboutMenu)
        helpMenu.add_command(label=langue['hi_score']+'...', underline=0, command= self.showHighscores)
        helpMenu.add_separator()
        helpMenu.add_command(label=langue['help'], underline=0, command= self.helpMenu)
        menuBar.add_cascade(label=langue['help'], menu=helpMenu)
        self.config(menu=menuBar)
    def canevasInit(self):
        "[]: Initialisation du canevas.\n Reserve a l'application."
        self.title(nomApp)
        self.canevas=Canvas(self, width=taille[0]*tailleCarreau+20, height=taille[1]*tailleCarreau+10, bg=apparence[0])
        self.canevas.bind("<Button-1>", self.pointeur)
        self.canevas.grid(row=1,columnspan=2,padx=2,pady=2)
    def helpMenu(self):
        "[]: Affiche le fichier d'aide"
        aboutWindow=Toplevel(width=300,height=500)
        aboutWindow.title(langue['help'])
        aboutWindow.grid()
        aboutWindow.resizable(width=0, height=0)
        Label(aboutWindow, text=loadFile(fileHelp),justify=LEFT,font=('Monaco',10)).grid(row=1)
    def settingsMenu(self):
        "[]: Affiche le menu d'edition des preferences"
        global nomJoueur,langue,preset,taille,tailleCarreau,apparence,colors,preferenceWindow,starts,level
        # - Variables utiles -
        if preferenceWindow:
            return False            # Il ne peut y avoir qu'une fenetre de preferences.
        else:
            preferenceWindow=True
        rw=0
        # Valeurs des combobox
        couleurs=['black','white','grey20','grey40','grey60','grey80','blue','purple','red','yellow','green']
        langues=['francais','english']
        ias=['neutral','aggro','deffensive','passive']
        levels=[1,0.8,0.6,0.5,0.35,0.2]
        # Valeurs affiches des combobox
        if langue['languageShort']=='Fr':
            couleursUser=['Noir','Blanc','Gris 20%','Gris 40%','Gris 60%','Gris 80%','Bleu','Violet','Rouge','Jaune','Vert']
            languesUser=['Francais','Anglais']
            iasUser=['Neutre','Aggresif','Defensif','Passif']
            levelUser=['Extreme','Difficile','Moyen','Facile','Kevin','Lamer']
        elif langue['languageShort']=='En':
            couleursUser=['Black','White','Grey 20%','Grey 40%','Grey 60%','Grey 80%','Blue','Purple','Red','Yellow','Green']
            languesUser=['French','English']
            iasUser=['Neutral','Aggresive','Deffensive','Passive']
            levelUser=['Dark Vador','Hard','Medium','Easy','Script Kiddie','Lame']
        settingsWindow=Toplevel(width=400,height=500)
        startsE=['cpu','player','random']
        startsU=[langue['cpu'],langue['player'],langue['random']]
        # %%%% PREFERENCES %%%%
        settingsWindow.title(langue['settings'])
        settingsWindow.grid()
        settingsWindow.resizable(width=0, height=0)
        #Label(settingsWindow, text="---------------------------",justify=LEFT,font=('Monaco',10),underline=1).grid(row=rw,sticky=W)
        # ----- GENERAL -----
        # -> Nom
        Label(settingsWindow, text=langue['general']+':',justify=LEFT,font=('Monaco',13),width=15,relief=RAISED).grid(row=rw,columnspan=2, sticky=W)
        rw+=1
        Label(settingsWindow, text=langue['player']+':').grid(row=rw,sticky=W)
        self.nomJoueurEntry=Entry(settingsWindow,width=30)
        self.nomJoueurEntry.insert(END, nomJoueur)
        self.nomJoueurEntry.grid(row=rw,column=1)
        # -> Langue
        rw+=1
        Label(settingsWindow, text=langue['language']+':').grid(row=rw,sticky=W)
        self.langueEntry=comboBox(settingsWindow,langue['languageDisplay'],langues,languesUser)
        self.langueEntry.grid(row=rw,column=1)
        # ----- APPARENCE -----
        # -> Fond
        rw+=1
        Label(settingsWindow, text=' ',justify=LEFT,font=('Monaco',4)).grid(row=rw)   # Espace
        rw+=1
        Label(settingsWindow, text=langue['appearance']+':',justify=LEFT,font=('Monaco',13),width=15,relief=RAISED).grid(row=rw,columnspan=2, sticky=W)
        rw+=1
        self.gridRoot = comboBox (settingsWindow,langue['grid'],couleurs,couleursUser)
        self.gridRoot.grid(row=rw,sticky=W)
        self.colorRoot = comboBox (settingsWindow,langue['color'],couleurs,couleursUser)
        self.colorRoot.grid(row=rw, column=1,sticky=W)
        # -> Pions
        rw+=1
        self.colorP1 = comboBox (settingsWindow,langue['color']+' '+langue['player'],couleurs,couleursUser)
        self.colorP1.grid(row=rw,column=0,sticky=W)
        self.gridP1 = comboBox (settingsWindow,langue['grid']+' '+langue['player'],couleurs,couleursUser)
        self.gridP1.grid(row=rw,column=1,sticky=W)
        # -> Pions 2
        rw+=1
        self.colorP2 = comboBox (settingsWindow,langue['color']+' '+langue['cpu'],couleurs,couleursUser)
        self.colorP2.grid(row=rw,column=0,sticky=W)
        self.gridP2 = comboBox (settingsWindow,langue['grid']+' '+langue['cpu'],couleurs,couleursUser)
        self.gridP2.grid(row=rw,column=1,sticky=W)
        # -> Taille
        rw+=1
        Label(settingsWindow, text=langue['size']+':').grid(row=rw,sticky=W)
        self.tailleDamier=Entry(settingsWindow,width=30)
        self.tailleDamier.insert(END, str(taille[0])+"x"+str(taille[1]))
        self.tailleDamier.grid(row=rw,column=1)
        # -> TailleCarreaux
        rw+=1
        Label(settingsWindow, text=langue['tile_size']+':').grid(row=rw,sticky=W)
        self.tailleCases=Entry(settingsWindow,width=30)
        self.tailleCases.insert(END, str(tailleCarreau))
        self.tailleCases.grid(row=rw,column=1)
        # ----- JEU -----
        # -> IA
        rw+=1
        Label(settingsWindow, text=' ',justify=LEFT,font=('Monaco',4)).grid(row=rw)   # Espace
        rw+=1
        Label(settingsWindow, text=langue['game']+':',justify=LEFT,font=('Monaco',13),width=15,relief=RAISED).grid(row=rw,columnspan=2, sticky=W)
        rw+=1
        Label(settingsWindow, text=langue['cpu']).grid(row=rw,sticky=W)
        self.ia = comboBox (settingsWindow,langue['gameplay'],ias,iasUser)
        self.ia.grid(row=rw,column=1,sticky=W)
        rw+=1
        self.levelEntry = comboBox (settingsWindow,langue['level'],levels,levelUser)
        self.levelEntry.grid(row=rw,column=1,sticky=W)
        # -> Commence
        rw+=1
        Label(settingsWindow, text=langue['w_starts']).grid(row=rw,sticky=W)
        self.startEntry=comboBox(settingsWindow,langue['w_starts'],startsE,startsU)
        self.startEntry.grid(row=rw,column=1,columnspan=2,sticky=W)
        # ----- FIN -----
        rw+=1
        Button(settingsWindow,text=langue['save'],command=lambda x=settingsWindow:self.changePrefsButton(x)).grid(row=rw,column=1,sticky=E)
    def aboutMenu(self):
        "Fenetre recensant les informations relatives au projet."
        aboutWindow=Toplevel(width=300,height=500)
        aboutWindow.title(langue['about_us'])
        aboutWindow.grid()
        aboutWindow.resizable(width=0, height=0)
        #- - - - - - - - - -
        # Logo
        #photo=PhotoImage(file="./Ressources/Logo.gif")
        #Label(aboutWindow, image=photo).grid(row=0) # Image de socitiete (ne fonctionne pas encore)
        #- - - - - - - - - -
        # Texte de remerciment & Copyleft
        Label(aboutWindow, text=loadFile(fileThanks),justify=LEFT,font=('Monaco',10)).grid(row=1)
    def changePrefsButton(self,window):
        "Fonction recuperant toutes les variables et les enregistrant suite a leur modification dans la fenetre de preferences"
        global nomJoueur,langue,preset,taille,tailleCarreau,apparence,colors,preferenceWindow,starts,level
        new_game=False
        # ---- Ensemble des variables (7) ----
        # -> Nom du joueur (1)
        nomJoueur=self.nomJoueurEntry.get()
        # -> Taille des carreaux (2)
        # Gestion des erreurs utilisateur
        try:                                                                            #   EXCEPTIONS:
            temp=eval(self.tailleCases.get())                                           #<- Test
        except NameError, x:                                                            #<- Si l'argument n'est pas un nombre
            showwarning(langue['error'], langue['err_notNumber'])                       #           (a,Kf ou autre)
            print "L'argument fourni n'est pas un nombre:  <"+str(x)+'>'                #
            temp=tailleCarreau                                                          #
        if type(temp)==int:
            if temp!=tailleCarreau and temp>9:
                new_game=True
                tailleCarreau=temp
        # -> Taille du Tableau (3)
        temp=self.tailleDamier.get().split('x')
        # Gestion des erreurs utilisateur                                               #   EXCEPTIONS
        try:                                                                            #
            x,y=eval(temp[0]),eval(temp[1])                                             #<- Test
        except (NameError,SyntaxError), x:                                              #
            showwarning(langue['error'], langue['err_notNumber'])                       #<- Si l'argument n'est pas un nombre 
            print "L'argument fourni n'est pas un nombre: <"+str(x)+'>'                 #           (a,Kf ou autre)
            x,y=taille[0],taille[1]                                                     #
        except IndexError, x:                                                           #
            showwarning(langue['error'], langue['err_incorrect']%langue['size'])        #<- Si l'argument ne contient pas 
            print "Vous n'avez pas fourni une taille au format [INT]x[INT]:", str(x)    #   deux valeurs
            x,y=taille[0],taille[1]
        if len(temp)>1:
            if type(x)==int and type(y)==int :
                if x!=taille[0] and x>4:
                    new_game=True
                    taille[0]=x
                if y!=taille[1] and y>4:
                    new_game=True
                    taille[1]=y
        # -> Apparence du Canevas (4)
        temp=self.gridRoot.valeur
        if temp!=False:
            apparence[1]=temp
        temp=self.colorRoot.valeur
        if temp!=False:
            apparence[0]=temp
        # Apparence des Pions (5)
        temp=self.colorP1.valeur
        if temp!=False:
            colors[0][0]=temp
        temp=self.gridP1.valeur
        if temp!=False:
            colors[0][1]=temp
        temp=self.colorP2.valeur
        if temp!=False:
            colors[1][0]=temp
        temp=self.gridP2.valeur
        if temp!=False:
            colors[1][1]=temp
        # -> Langue (6)
        temp=self.langueEntry.valeur
        if temp!=False:
            langue=eval(changePreset(temp))
        # -> Preset (7)
        temp=self.ia.valeur
        if temp!=False:
            try:
                preset=eval(changePreset(temp))
                new_game=True
            except SyntaxError, x:
                print "Le fichier n'est pas correct !"
                print x
                showwarning(langue['error'], langue['err_incorrect']%langue['file'])
            except:
                print "Erreur inconnue:"
                print x
        # -> Difficultee
        temp=self.levelEntry.valeur
        if temp!=False:
            new_game=True
            level=temp
        # -> Starts (9)
        temp=self.startEntry.valeur
        if temp!=False:
            starts=temp
        savePrefs()
        window.destroy()
        preferenceWindow=False      #On peut a nouveau ouvrir une fenetre de preferences
        if new_game:
            self.newGame()
    def newGame(self):
        "Fonction appelee a chaque nouvelle partie, nettoie le canevas, charge les variables, reinitialise les tableaux et ecrase les fichiers excedentaires."
        global tour,case,C2QTPL,evalC,TQTPL,evalQ,game,playedCases
        global time_0,time_F
        #t=datetime.datetime.now()
        #time_0=time.mktime(t.timetuple())
        time_0=time.clock()
        # Initialisations des variables
        self.clearAll()
        lignes,colones,i=taille[1],taille[0],0
        case=[0]*(lignes*colones)
        tour=0
        playedCases=[]
        # Generation des tables IA:
        TQTPL,C2QTPL,evalC,evalQ=generationTables()
        dp(TQTPL)
        # Generation du canevas
        self.canevas.config(width=colones*tailleCarreau,height=lignes*tailleCarreau)
        self.canevas.config(bg=apparence[0])
        self.canevas.create_line(0,4,colones*tailleCarreau+2,4,fill=apparence[1])
        self.canevas.create_line(3,0,3,lignes*tailleCarreau+3,fill=apparence[1])
        while lignes+1>i:
            self.canevas.create_line(0,i*tailleCarreau+3,colones*tailleCarreau+2,i*tailleCarreau+3,fill=apparence[1])
            i+=1
        i=0
        while colones+1>i:
            self.canevas.create_line(i*tailleCarreau+2,0,i*tailleCarreau+2,lignes*tailleCarreau+3,fill=apparence[1])
            i+=1
        # Premier coup
        game=True
        boo=0
        dp("EvalC post-generation")
        printEval(evalC)
        if starts == 'random':
            boo= randint(0,1)
        if starts=='cpu' or boo==1:
            nCase=choixcaseIA()
            #######
            # Positionement
            y=nCase/taille[0]
            x=nCase-y*(taille[0])
            case[x+taille[0]*(y)]=2
            self.placerPion(x,y,1)
            self.canevas.delete(self.der)
            # Maj
            playedCases+=[[nCase,evalC[nCase],self.der+1]]
            majTables(nCase)
            dp("Post-IA tour %i"%(tour-1))
            printEval(evalC)
    def undo(self):
        global playedCases,tour,game
        # Annulation IA
        #printEval(evalC)
        if game:
            if tour<1:
                return False
            majTables(playedCases[-1][0],-1)
            case[playedCases[-1][0]]=0
            evalC[playedCases[-1][0]]=playedCases[-1][1]
            self.canevas.delete(playedCases[-1][2]) # Le coup IA precedent
            playedCases.pop(-1)
            # Annulation Humain
            majTables(playedCases[-1][0],-1)
            case[playedCases[-1][0]]=0
            evalC[playedCases[-1][0]]=playedCases[-1][1]
            self.canevas.delete(playedCases[-1][2]) # Le coup HUMAIN du dessus
            playedCases.pop(-1)
            # Fin annulation
            dp("Cases joues"+str(playedCases))
            printEval(evalC)
            self.canevas.delete(self.der) # Le surlignage precedent
            # On replace un pion pour que tout rentre dans l'ordre
            if tour>2:
                self.canevas.delete(playedCases[-1][2]) # Le coup IA Precedent
                nCase=playedCases[-1][0]
                y=nCase/taille[0]
                x=nCase-y*(taille[0])
                self.placerPion(x,y,(tour+1)%2)
                playedCases[-1][2]=self.der+1
                #self.canevas.delete(self.der)
            else:
                print "Impossible d'annuler !"
            tour -=2
    def clearAll(self):
        "Efface toutes les variables inutiles..."
        self.canevas.delete(ALL)
        clearAllVars()
        loadPrefs()
    def pointeur(self,event):
        "[event]: Detecte les evenements sur le canevas, ici le clic de souris.\n Reserve a l'application"
        global case,playedCases
        x,y=event.x/tailleCarreau,event.y/tailleCarreau
        dp("Click detecte en [%i,%i] code: [%i]"%(x,y,x+taille[0]*(y)))
        x,y=event.x/tailleCarreau,event.y/tailleCarreau
        if x<taille[0] and y<taille[1] and game:
            dp("Verification")
            if case[x+taille[0]*(y)]==0:
                self.placerPion(x,y,tour%2)
                playedCases+=[[x+taille[0]*y,evalC[x+taille[0]*y],self.der+1]]
                case[x+taille[0]*(y)]=tour%2+1
                self.jeuIA(x,y)
            else:
                dp("Deja pris")
    def placerPion(self,x,y,joueur):
        "[int,int,(0or1)]: Place un pion en position x,y NUMEROTES A PARTIR DE 0 !\n N'effectue pas de verifications !!"
        r=tailleCarreau/3
        x_p=x*tailleCarreau+(tailleCarreau+5)/2
        y_p=y*tailleCarreau+(tailleCarreau+5)/2
        if tour > 0:
            self.canevas.delete(self.der)
        outl=colors[joueur][0]
        if colors[joueur][1]==colors[joueur][0]:
            if colors[(joueur+1)%2][1]=='red' and colors[(joueur+1)%2][0]==colors[joueur][0]:
                outl='darkblue'
            else:
                outl='red'
        dp("Cases joues"+str(playedCases))
        self.der=self.canevas.create_oval(x_p-r-2,y_p-r-2,x_p+r+2,y_p+r+2,fill=outl,outline=outl)
        self.canevas.create_oval(x_p-r,y_p-r,x_p+r,y_p+r,fill=colors[joueur][0],outline=colors[joueur][1])
    def jeuIA(self,x,y):
        global evalC,case,playedCases
        nCase=x+(taille[0])*y
        majTables(nCase)
        if game:        # Si on a gagne inutile de continuer
            global tour
            dp("Post-Joueur tour %i"%tour)
            printEval(evalC)
            tour+=1     #   On avance d'un tour
            ########
            # IA
            nCase=choixcaseIA()
            if nCase<0:
            	return False
            ########
            # Traitement post IA
            playedCases+=[[nCase,evalC[nCase],self.der+3]]
            evalC[nCase]=-1
            case[nCase]=2
            #######
            # Positionement
            y=nCase/taille[0]
            x=nCase-y*(taille[0])
            case[x+taille[0]*(y)]=tour%2+1
            self.placerPion(x,y,1)
            # Maj
            majTables(nCase)
            dp("Post-IA tour %i"%tour)
            printEval(evalC)
            tour+=1     #   On re-avance d'un tour
    def Gagne(self,player,qtpl):
        #t=datetime.datetime.now()
        #time_F=time.mktime(t.timetuple())-time_0
        time_F=time.clock()
        dp(time_F)
        mint,sec=int(time_F)/60,time_F-int(time_F)/60
        if player==0:
            titre=langue['win']
            txt=langue['win_player']%(mint,sec)
        else:
            titre=langue['loose']
            txt=langue['win_cpu']
        #print qtpl[0]
        showinfo(titre, txt)
        y1,y2=qtpl[0]/taille[0],qtpl[4]/taille[0]
        x1,x2=qtpl[0]-y1*(taille[0]),qtpl[4]-y2*(taille[0])
        x1,x2,y1,y2=decalage(x1),decalage(x2),decalage(y1),decalage(y2)
        self.canevas.create_line(x1,y1,x2,y2,fill=colors[player][1],width=tailleCarreau/6+2,joinstyle=ROUND,capstyle=ROUND)
        self.canevas.create_line(x1,y1,x2,y2,fill=colors[player][0],width=tailleCarreau/6,joinstyle=ROUND,capstyle=ROUND)
        self.canevas.delete(self.der)
        if player==0:   # On ne gagne que si on est gagnant ...
            addHighscore(time_F)
            self.showHighscores()
    def showHighscores(self):
        res=readHighscore()
        txt= '+----------------------+--------+----------+-------+-----+\n'
        if langue['languageShort']=='Fr':
            txt+='| Nom                  | Taille | Score    | Temps |Level|\n'
        elif langue['languageShort']=='En':
            txt+='| Name                 | Size   | Score    | Time  |Level|\n'
        txt+='+----------------------+--------+----------+-------+-----+\n'
        for NOMJ,RES,SCORE,TIME,LEVEL in res:
            #print NOMJ,RES,SCORE,TIME,LEVEL
            txt+='| '+NOMJ[:20]+' '*(20-len(NOMJ))+' | '
            txt+=RES+' '*(6-len(RES))+' | '
            txt+=str(SCORE)+' '*(8-len(str(SCORE)))+' | '
            mint,sec=TIME/60,TIME%60
            txt+=(str(mint)+':'+str(sec))+' '*(5-len(str(mint)+':'+str(sec)))+' | '
            txt+=str(LEVEL)+' '*(2-len(str(LEVEL)))+'  | \n'
        txt+='+----------------------+--------+----------+-------+-----+'
        hsWindow=Toplevel(width=300)
        hsWindow.title(langue['hi_score'])
        hsWindow.grid()
        hsWindow.resizable(width=0,height=0)
        Label(hsWindow, text=txt,justify=LEFT,font=('Monaco',10)).grid(row=1)
    def nul(self):
    	global game
        titre=langue['tie']
        txt=langue['end_tie']
        game=False
        showinfo(titre, txt)
#########################
# Classe supplementaire #
#########################
def decalage(x):
    return x*tailleCarreau+(tailleCarreau+5)/2
class comboBox(Menubutton):
    """Nouvelle classe de menu ayant pour but de ENFIN disposer d'une combobox digne de ce nom !
    [Tk,str,array]:[fenetre, nom, valeurs]
    La seule valeur importante est sa .valeur qui est celle selectionnee par l'utilisateur"""
    def __init__(self, window, nom, valeurs, nomValeurs=False):
        "Fonction d'initialisation, prepare le module."
        if nomValeurs==False:
            nomValeurs=valeurs  #Par defaut, le nom des valeurs est le meme que le nom affiche
        self.valeur=False
        Menubutton.__init__(self,window, text=nom)
        #self.config(text=nom)
        menu1=Menu(self)
        i,maxi=0,len(valeurs)
        while i <maxi:
            if valeurs[i] == 'separator':
                menu1.add_separator()
            else:
                menu1.add_command(label=nomValeurs[i], underline=0, command=lambda text=valeurs[i]:self.changeVal(text))
            i+=1
        self.configure(menu=menu1)
    def changeVal(self,nom):
        self.valeur=nom

#- - - - - - - - - - - -
# Les fonctions utiles sont:
#- - - - - -
# Pour placer un pion:
# Application.placerPion(x,y,joueur)
# Nouvelle partie:
# Application.newGame()